﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Runtime.InteropServices;

using Window = System.Windows.Window;
using Size = System.Drawing.Size;
using Vector = System.Windows.Vector;

namespace LibSnesDotNet.UI
{
	public abstract class HybridForm : Form
	{
		#region Win32 Stuff

		const int WM_ACTIVATE = 0x006;
		const int WM_ACTIVATEAPP = 0x01C;
		const int WM_NCACTIVATE = 0x086;
		const int WM_KILLFOCUS = 0x0008;
		const int WM_SETFOCUS = 0x7;
		const int WM_WINDOWPOSCHANGING = 0x0046;
		const int WM_SYSCOMMAND = 0x0112;
		const int WS_EX_TOOLWINDOW = 0x00000080;
		const int GWL_EXSTYLE = (-20);
		const int SWP_NOSIZE = 0x0001;
		const int SWP_NOMOVE = 0x0002;
		const int SWP_NOACTIVATE = 0x0010;
		const int SC_MINIMIZE = 0xF020;
		const int SC_RESTORE = 0xF120;
		const int SC_SCREENSAVE = 0xF140;
		const int SC_MONITORPOWER = 0xF170;
		const int SC_KEYMENU = 0xF100;
		const int WA_INACTIVE = 0;
		const int WA_ACTIVE = 1;
		const int WA_CLICKACTIVE = 2;
		static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
		static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
		static readonly IntPtr HWND_TOP = new IntPtr(0);
		static readonly IntPtr HWND_BOTTOM = new IntPtr(1);

		[StructLayout(LayoutKind.Sequential)]
		struct WINDOWPOS
		{
			public IntPtr hwnd;
			public IntPtr hwndInsertAfter;
			public int x;
			public int y;
			public int cx;
			public int cy;
			public int flags;
		}

		[DllImport("user32.dll")]
		extern static int SendMessage(IntPtr handle, int msg, int wParam, IntPtr lParam);
		[DllImport("user32.dll")]
		extern static int PostMessage(IntPtr handle, int msg, int wParam, IntPtr lParam);
		[DllImport("user32.dll", EntryPoint  ="GetWindowLong")]
		public static extern int GetWindowLong32(IntPtr hWnd, int nIndex);
		[DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
		public static extern long GetWindowLong64(IntPtr hWnd, int index);
		[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
		private static extern int SetWindowLong32(IntPtr hWnd, int index, int value);
		[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
		private static extern long SetWindowLong64(IntPtr hWnd, int index, long value);
		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

		private static IntPtr GetWindowLong(IntPtr hWnd, int index)
		{
			switch (IntPtr.Size)
			{
				case 4:
					return (IntPtr)GetWindowLong32(hWnd, index);

				case 8:
					return (IntPtr)GetWindowLong64(hWnd, index);
			}

			throw new NotSupportedException();
		}

		private static IntPtr SetWindowLong(IntPtr hWnd, int index, IntPtr value)
		{
			switch (IntPtr.Size)
			{
				case 4:
					return (IntPtr)SetWindowLong32(hWnd, index, (int)value);

				case 8:
					return (IntPtr)SetWindowLong64(hWnd, index, (long)value);
			}

			throw new NotSupportedException();
		}

		#endregion

		OverlayWindow _overlay;
		System.Windows.Interop.HwndSource _overlaySource;
		Size _chromeTopLeft;
		Size _chromeBottomRight;
		Vector _wpfScale;

		public HybridForm(OverlayWindow overlay)
		{
			SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true);

			_overlay = overlay;
			_overlay.BaseForm = this;
			System.Windows.Forms.Integration.ElementHost.EnableModelessKeyboardInterop(_overlay);
			_overlay.Loaded += Overlay_Loaded;
			_overlay.PreviewKeyDown += Overlay_PreviewKeyDown;
		}

		public event EventHandler AppActivated;
		public event EventHandler AppDeactivated;
		protected OverlayWindow Overlay { get { return _overlay; } }
		public Size ChromeTopLeft { get { return _chromeTopLeft; } }
		public Size ChromeBottomRight { get { return _chromeBottomRight; } }

		protected virtual void CoerceClientSize(ref Size newSize)
		{
		}

		protected virtual void OnAppActivated(EventArgs e)
		{
			if (AppActivated != null) { AppActivated(this, e); }
		}

		protected virtual void OnAppDeactivated(EventArgs e)
		{
			if (AppDeactivated != null) { AppDeactivated(this, e); }
		}

		protected override void OnLoad(EventArgs e)
		{
			var clientRect = RectangleToScreen(ClientRectangle);
			_chromeTopLeft = new Size(clientRect.Left - Left, clientRect.Top - Top);
			_chromeBottomRight = new Size(Right - clientRect.Right, Bottom - clientRect.Bottom);

			_overlay.Left = (Left + _chromeTopLeft.Width) * _wpfScale.X;
			_overlay.Top = (Top + _chromeTopLeft.Height) * _wpfScale.Y;
			_overlay.Width = ClientSize.Width * _wpfScale.X;
			_overlay.Height = ClientSize.Height * _wpfScale.Y;
			_overlay.Show();

			base.OnLoad(e);
		}

		protected override void OnMove(EventArgs e)
		{
			base.OnMove(e);

			switch (FormBorderStyle)
			{
				case FormBorderStyle.Sizable:
					_overlay.Left = (Left + _chromeTopLeft.Width) * _wpfScale.X;
					_overlay.Top = (Top + _chromeTopLeft.Height) * _wpfScale.Y;
					break;

				case FormBorderStyle.None:
					_overlay.Left = Left * _wpfScale.X;
					_overlay.Top = Top * _wpfScale.Y;
					break;
			}
		}

		protected override void OnSizeChanged(EventArgs e)
		{
			base.OnSizeChanged(e);

			_overlay.Width = ClientSize.Width * _wpfScale.X;
			_overlay.Height = ClientSize.Height * _wpfScale.Y;
		}

		protected override void WndProc(ref Message m)
		{
			switch (m.Msg)
			{
				case WM_SYSCOMMAND:
					if (((int)m.WParam == SC_SCREENSAVE || ((int)m.WParam == SC_MONITORPOWER && (int)m.LParam >= 1)))
					{
						m.Result = IntPtr.Zero;
						return;
					}
					break;

				case WM_WINDOWPOSCHANGING:
					unsafe
					{
						WINDOWPOS* windowPos = (WINDOWPOS*)m.LParam;

						if ((windowPos->flags & SWP_NOSIZE) != SWP_NOSIZE)
						{
							Size newSize = new Size(windowPos->cx - (_chromeTopLeft.Width + _chromeBottomRight.Width),
							                        windowPos->cy - (_chromeTopLeft.Height + _chromeBottomRight.Height));

							CoerceClientSize(ref newSize);

							windowPos->cx = newSize.Width + (_chromeTopLeft.Width + _chromeBottomRight.Width);
							windowPos->cy = newSize.Height + (_chromeTopLeft.Height + _chromeBottomRight.Height);
						}
					}
					break;

				case WM_ACTIVATEAPP:
					if ((int)m.WParam == 1)
					{
						OnAppActivated(new EventArgs());
					}
					else if ((int)m.WParam == 0)
					{
						OnAppDeactivated(new EventArgs());
					}
					break;
			}

			if (_overlaySource != null && _overlay.IsLoaded && _overlay.Visibility == System.Windows.Visibility.Visible)
			{
				switch (m.Msg)
				{
					case WM_WINDOWPOSCHANGING:
						unsafe
						{
							WINDOWPOS* windowPos = (WINDOWPOS*)m.LParam;

							windowPos->hwndInsertAfter = _overlaySource.Handle;
						}
						break;

					case WM_ACTIVATE:
						if ((short)m.WParam == WA_ACTIVE || (short)m.WParam == WA_CLICKACTIVE)
						{
							SendMessage(Handle, WM_NCACTIVATE, 1, IntPtr.Zero);

							SetWindowPos(_overlaySource.Handle, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
							SetWindowPos(Handle, _overlaySource.Handle, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
						}
						else if ((short)m.WParam == WA_INACTIVE)
						{
							if (m.LParam == _overlaySource.Handle)
							{
								SendMessage(Handle, WM_NCACTIVATE, 1, IntPtr.Zero);
							}
							else
							{
								PostMessage(Handle, WM_NCACTIVATE, 0, IntPtr.Zero);
							}
						}
						break;

					case WM_ACTIVATEAPP:
						if ((int)m.WParam == 1)
						{
							_overlay.Left = Left + _chromeTopLeft.Width;
							_overlay.Top = Top + _chromeTopLeft.Height;
							_overlay.Width = ClientSize.Width;
							_overlay.Height = ClientSize.Height;

							SetWindowPos(_overlaySource.Handle, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
							SetWindowPos(Handle, _overlaySource.Handle, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
						}
						else if ((int)m.WParam == 0)
						{
							SetWindowPos(_overlaySource.Handle, Handle, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
							SetWindowPos(Handle, _overlaySource.Handle, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
						}
						break;

					case WM_SYSCOMMAND:
						switch ((int)m.WParam)
						{
							case SC_MINIMIZE:
								PostMessage(_overlaySource.Handle, WM_SYSCOMMAND, SC_MINIMIZE, IntPtr.Zero);
								break;

							case SC_RESTORE:
								SendMessage(_overlaySource.Handle, WM_SYSCOMMAND, SC_RESTORE, IntPtr.Zero);

								Focus();
								break;
						}
						break;
				}
			}

			base.WndProc(ref m);
		}

		void Overlay_Loaded(object sender, System.Windows.RoutedEventArgs e)
		{
			_overlaySource = (System.Windows.Interop.HwndSource)System.Windows.PresentationSource.FromVisual(_overlay);
			int exStyle = (int)GetWindowLong(_overlaySource.Handle, GWL_EXSTYLE);
			exStyle |= WS_EX_TOOLWINDOW;
			SetWindowLong(_overlaySource.Handle, GWL_EXSTYLE, (IntPtr)exStyle);

			SetWindowPos(_overlaySource.Handle, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
			SetWindowPos(Handle, _overlaySource.Handle, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);

			var m = _overlaySource.CompositionTarget.TransformFromDevice;
			_wpfScale.X = m.M11;
			_wpfScale.Y = m.M22;

			_overlay.Left = (Left + _chromeTopLeft.Width) * _wpfScale.X;
			_overlay.Top = (Top + _chromeTopLeft.Height) * _wpfScale.Y;
			_overlay.Width = ClientSize.Width * _wpfScale.X;
			_overlay.Height = ClientSize.Height * _wpfScale.Y;

			_overlaySource.AddHook(Overlay_WndProc);
		}

		void Overlay_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
		{
			if (e.KeyboardDevice.Modifiers == System.Windows.Input.ModifierKeys.Alt)
			{
				switch (e.SystemKey)
				{
					case System.Windows.Input.Key.Enter:
						e.Handled = true;
						Focus();
						SendKeys.Send("%~");
						break;

					case System.Windows.Input.Key.Space:
						e.Handled = true;
						Focus();
						SendKeys.Send("% ");
						break;

					case System.Windows.Input.Key.F4:
						e.Handled = true;
						Focus();
						SendKeys.Send("%{F4}");
						break;
				}
			}
		}

		IntPtr Overlay_WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
		{
			if (_overlay.IsLoaded && _overlay.Visibility == System.Windows.Visibility.Visible)
			{
				if (msg == WM_ACTIVATE)
				{
					if ((short)wParam == WA_ACTIVE || (short)wParam == WA_CLICKACTIVE)
					{
						SendMessage(Handle, WM_NCACTIVATE, 1, IntPtr.Zero);
					}
					else if ((short)wParam == WA_INACTIVE && lParam != Handle)
					{
						PostMessage(Handle, WM_NCACTIVATE, 0, IntPtr.Zero);
					}
				}
			}

			handled = false;
			return IntPtr.Zero;
		}
	}
}
